/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package co.edu.sd.jxta.msn;

import co.edu.sd.jxta.service.ServiceType;
import co.edu.sd.jxta.util.DateUtil;
import co.edu.sd.jxta.util.FileUtil;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.jxta.document.MimeMediaType;
import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.Message.ElementIterator;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.endpoint.WireFormatMessage;
import net.jxta.endpoint.WireFormatMessageFactory;

/**
 *
 * @author SD
 */
public class MessageMSPFactory {

    private final static MimeMediaType GZIP_MEDIA_TYPE = new MimeMediaType("application/gzip").intern();

    /**
     * Adds a String to a Message as a StringMessageElement
     *
     * @param message   The message to add to
     * @param nameSpace The namespace of the element to add. a null value assumes default namespace.
     * @param elemName  Name of the Element.
     * @param string    The string to add
     */
    public static void addStringToMessage(Message message, String nameSpace, String elemName, String content) {
        message.addMessageElement(nameSpace, null);
    }

    public static void addStringToMessage(Message message, String elemName, String content) {
        addStringToMessage(message, null, elemName, content);
    }
    public static void addUnsecureStringToMessage(Message message, String nameSpace, String elemName, String content) {
        message.addMessageElement(nameSpace, new StringMessageElement(elemName, (content), null));
    }

    public static void addUnsecureStringToMessage(Message message, String elemName, String content) {
        addUnsecureStringToMessage(message, null, elemName, content);
    }


    /**
     * Adds a long to a message
     *
     * @param message   The message to add to
     * @param nameSpace The namespace of the element to add. a null value assumes default namespace.
     * @param elemName  Name of the Element.
     * @param data      The feature to be added to the LongToMessage attribute
     */
    public static void addLongToMessage(Message message, String nameSpace, String elemName, long data) {
        message.addMessageElement(nameSpace, new StringMessageElement(elemName, Long.toString(data), null));
    }

    /**
     * Adds a int to a message
     *
     * @param message   The message to add to
     * @param nameSpace The namespace of the element to add. a null value assumes default namespace.
     * @param elemName  Name of the Element.
     * @param data      The feature to be added to the IntegerToMessage attribute
     */
    public static void addIntegerToMessage(Message message, String nameSpace, String elemName, int data) {
        message.addMessageElement(nameSpace, new StringMessageElement(elemName, Integer.toString(data), null));
    }

    public static void addIntegerToMessage(Message message, String elemName, int data) {
        message.addMessageElement(null, new StringMessageElement(elemName, Integer.toString(data), null));
    }

    /**
     * Adds an byte array to a message
     *
     * @param message   The message to add to
     * @param nameSpace The namespace of the element to add. a null value assumes default namespace.
     * @param elemName  Name of the Element.
     * @param data      the byte array
     * @param compress  indicates whether to use GZIP compression
     * @throws IOException if an io error occurs
     */
    public static void addByteArrayToMessage(Message message, String nameSpace, String elemName, byte[] data, boolean compress) throws IOException {
        byte[] buffer = data;
        MimeMediaType mimeType = MimeMediaType.AOS;

        if (compress) {
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            GZIPOutputStream gos = new GZIPOutputStream(outStream);

            gos.write(data, 0, data.length);
            gos.finish();
            gos.close();
            buffer = outStream.toByteArray();
            mimeType = GZIP_MEDIA_TYPE;
        }
        message.addMessageElement(nameSpace, new ByteArrayMessageElement(elemName, mimeType, buffer, null));
    }

    public static void addByteArrayToMessage(Message message, String elemName, byte[] data, boolean compress) throws IOException {
        addByteArrayToMessage(message, null, elemName, data, compress);
    }

    /**
     * Adds an Object to message within the specified name space and with the specified element name
     * @param message   the message to add the object to
     * @param nameSpace the name space to add the object under
     * @param elemName  the given element name
     * @param object    the object
     * @throws IOException if an io error occurs
     */
    public static void addObjectToMessage(Message message, String nameSpace, String elemName, Object object) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(object);
        oos.close();
        bos.close();
        addByteArrayToMessage(message, nameSpace, elemName, bos.toByteArray(), false);
    }

    /**
     * Returns a String from a message
     *
     * @param message   The message to retrieve from
     * @param nameSpace The namespace of the element to get.
     * @param elemName  Name of the Element.
     * @return The string value or {@code null} if there was no element matching the specified name.
     */
    public static String getStringFromMessage(Message message, String nameSpace, String elemName) {
        MessageElement me = message.getMessageElement(nameSpace, elemName);

        return me.toString();
    }

    public static String getStringFromMessage(Message message, String elemName) {
        return getStringFromMessage(message, null, elemName);
    }


     public static String getUnsecureStringFromMessage(Message message, String nameSpace, String elemName) {
        MessageElement me = message.getMessageElement(nameSpace, elemName);

        if (null != me) {
            return (me.toString());
        } else {
            return null;
        }
    }

    public static String getUnsecureStringFromMessage(Message message, String elemName) {
        return getUnsecureStringFromMessage(message, null, elemName);
    }

    /**
     * Returns an long from a message
     *
     * @param message   The message to retrieve from
     * @param nameSpace The namespace of the element to get.
     * @param elemName  Name of the Element.
     * @return The long value
     * @throws NumberFormatException If the String does not contain a parsable int.
     */
    public static long getLongFromMessage(Message message, String nameSpace, String elemName) throws NumberFormatException {
        String longStr = getStringFromMessage(message, nameSpace, elemName);

        if (null != longStr) {
            return Long.parseLong(longStr);
        } else {
            throw new NumberFormatException("No such Message Element.");
        }
    }

    /**
     * Returns an int from a message
     *
     * @param message   The message to retrieve from
     * @param nameSpace The namespace of the element to get.
     * @param elemName  Name of the Element.
     * @return The int value
     * @throws NumberFormatException If the String does not contain a parsable long.
     */
    public static int getIntegerFromMessage(Message message, String nameSpace, String elemName) throws NumberFormatException {
        String intStr = getStringFromMessage(message, nameSpace, elemName);

        if (null != intStr) {
            return Integer.parseInt(intStr);
        } else {
            throw new NumberFormatException("No such Message Element.");
        }
    }

    public static int getIntegerFromMessage(Message message, String elemName) throws NumberFormatException {
        return getIntegerFromMessage(message, null, elemName);
    }

    /**
     * Returns an InputStream for a byte array
     *
     * @param message   The message to retrieve from
     * @param nameSpace The namespace of the element to get.
     * @param elemName  Name of the Element.
     * @return The {@code InputStream} or {@code null} if the message has no such element, String elemName) throws IOException {
     * @throws IOException if an io error occurs
     */
    public static InputStream getInputStreamFromMessage(Message message, String nameSpace, String elemName) throws IOException {
        InputStream result = null;
        MessageElement element = message.getMessageElement(nameSpace, elemName);

        if (null == element) {
            return null;
        }

        if (element.getMimeType().equals(GZIP_MEDIA_TYPE)) {
            result = new GZIPInputStream(element.getStream());
        } else if (element.getMimeType().equals(MimeMediaType.AOS)) {
            result = element.getStream();
        }
        return result;
    }

    /**
     * Reads a single Java Object from a Message.
     *
     * @param message   The message containing the object.
     * @param nameSpace The name space of the element containing the object.
     * @param elemName  The name of the element containing the object.
     * @return The Object or {@code null} if the Message contained no such element.
     * @throws IOException if an io error occurs
     * @throws ClassNotFoundException if an object could not constructed from the message element
     */
    public static Object getObjectFromMessage(Message message, String nameSpace, String elemName) throws IOException, ClassNotFoundException {
        InputStream is = getInputStreamFromMessage(message, nameSpace, elemName);

        if (null == is) {
            return null;
        }
        ObjectInputStream ois = new ObjectInputStream(is);

        return ois.readObject();
    }

    public static Message createFileDownloadMessage(String sOwner, String sFilename) {
        Message oMessage = new Message();
        addStringToMessage(oMessage, null, "" + Attributes.TAG_PROCESS_ID, "" + ServiceType.FILE_DOWNLOAD.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OWNER, sOwner);
        addStringToMessage(oMessage, null, "" + Attributes.TAG_FILE_NAME, sFilename);
        //addByteArrayToMessage(oMessage, null, Attributes.TAG_FILE_CONTENT.name(), FileUtil.getBase64EncodeFromFile(sFilename).getBytes(), false);
        return oMessage;
    }

    public static Message createFileSearchRequestMessage(String sOwner, String sFilename) {
        Message oMessage = new Message();
        addStringToMessage(oMessage, null, "" + Attributes.TAG_PROCESS_ID, "" + ServiceType.FILE_SEARCH.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OWNER, sOwner);
        addStringToMessage(oMessage, null, "" + Attributes.TAG_FLOW_STEP, "1");
        addStringToMessage(oMessage, null, "" + Attributes.TAG_FILE_NAME, sFilename);
        return oMessage;
    }

    public static Message createFileSearchResponseMessage(String sOwner, boolean bFound) {
        Message oMessage = new Message();
        addStringToMessage(oMessage, null, "" + Attributes.TAG_PROCESS_ID, "" + ServiceType.FILE_SEARCH.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OWNER, sOwner);
        addStringToMessage(oMessage, null, "" + Attributes.TAG_FLOW_STEP, "2");
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OPERATION_RESULT, "" + bFound);
        return oMessage;
    }

    public static Message createOperationSuccessMessage(String sOwner, String sForService) {
        Message oMessage = new Message();
        addStringToMessage(oMessage, null, "" + Attributes.TAG_PROCESS_ID, "" + ServiceType.GENERAL_MESSAGE.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_RESPONSE_FOR, sForService);
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OWNER, sOwner);
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OPERATION_RESULT, "true");
        return oMessage;
    }

    public static Message createExecuteApplicationMessageRequest(String sOwner, String command) {
        Message oMessage = new Message();
        addStringToMessage(oMessage, null, "" + Attributes.TAG_PROCESS_ID, "" + ServiceType.APP_EXEC.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OWNER, sOwner);
        addStringToMessage(oMessage, null, "" + Attributes.TAG_APP_COMMAND, command);
        return oMessage;
    }

    public static Message createExecuteApplicationMessageResponse(String sOwner, String output) {
        Message oMessage = new Message();
        addStringToMessage(oMessage, null, "" + Attributes.TAG_PROCESS_ID, "" + ServiceType.APP_EXEC.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_OWNER, sOwner);
        addStringToMessage(oMessage, null, "" + Attributes.TAG_APP_OUTPUT, output);
        return oMessage;
    }

    public static Message createTimeMessageRequest(String sOwner) {
        Message oMessage = createDefaultMessage(sOwner, ServiceType.UTC_TIME.getName());
        return oMessage;
    }

    public static Message createTimeMessageResponse(String sOwner, String output) {
        Message oMessage = createDefaultMessage(sOwner, ServiceType.UTC_TIME.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_TIME_OUTPUT, output);
        return oMessage;
    }

    public static Message createUTCTimeMessage() throws IOException {
        Message oMessage = new Message();
        addStringToMessage(oMessage, null, "" + Attributes.TAG_PROCESS_ID, "" + ServiceType.GENERAL_MESSAGE.getName());
        addLongToMessage(oMessage, null, Attributes.TAG_VALUE.name(), DateUtil.getCurrentTimeUTC());
        return oMessage;
    }

    public static Message createGlobalStateMarkerMessage(String sOwner) {
        Message oMessage = createDefaultMessage(sOwner, ServiceType.SAVE_STATE.getName());

        addStringToMessage(oMessage, null, "" + Attributes.TAG_STATE_FLOW_ID, "" + StateTags.MARK);
        return oMessage;
    }

    public static Message createGlobalStateACKMessage(String sOwner, List localstate) {
        Message oMessage = createDefaultMessage(sOwner, ServiceType.SAVE_STATE.getName());
        addStringToMessage(oMessage, null, "" + Attributes.TAG_STATE_FLOW_ID, "" + StateTags.ACK);
        try {
            addObjectToMessage(oMessage, null, "" + Attributes.TAG_MESSAGES, localstate);
        } catch (IOException ex) {
            Logger.getLogger(MessageMSPFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
        return oMessage;
    }

    public static Message createFileUploadMessage(String sOwner, String sFilename) {
        Message oMessage = createDefaultMessage(sOwner, ServiceType.FILE_UPLOAD.getName());
        File oFile = new File(sFilename);
        addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
        try {
            addByteArrayToMessage(oMessage, "" + Attributes.TAG_FILE_CONTENT, FileUtil.getBase64EncodeFromFile(sFilename).getBytes(), false);
        } catch (IOException ex) {
            Logger.getLogger(MessageMSPFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
        return oMessage;
    }
    private static int pieceSize = 1000;

    public static List createFileUploadMessagePieces(String sOwner, String sFilename) {
        File oFile = new File(sFilename);
        String sFileContent = "";
        try {
            sFileContent = FileUtil.getBase64EncodeFromFile(sFilename);
        } catch (IOException ex) {
            Logger.getLogger(MessageMSPFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
        int pieces = sFileContent.length() / pieceSize;
        List<Message> messages = new ArrayList<Message>();
        System.out.println("pieces required:" +pieces);
        for (int i = 0; i < pieces; i++) {
            Message oMessage = createDefaultMessage(sOwner, ServiceType.FILE_UPLOAD.getName());
            addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
            //addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
            addIntegerToMessage(oMessage, "" + Attributes.TAG_PIECE_ID, i);
            addStringToMessage(oMessage, "" + Attributes.TAG_MESSAGE_ID, sOwner + "_UPLOAD_" + i);
            StringBuffer sCurrentContent = new StringBuffer();
            if (((i + 1) * pieceSize) < sFileContent.length()) {
                sCurrentContent = new StringBuffer(sFileContent.substring(pieceSize * i, pieceSize * (i + 1)));
            } else {
                sCurrentContent = new StringBuffer(sFileContent.substring(pieceSize * i, sFileContent.length()));
            }
            try {
                addByteArrayToMessage(oMessage, "" + Attributes.TAG_FILE_CONTENT, sCurrentContent.toString().getBytes(), false);
            } catch (IOException ex) {
                Logger.getLogger(MessageMSPFactory.class.getName()).log(Level.SEVERE, null , ex);
                
            }
            messages.add(oMessage);
        }
        return messages;
    }

    public static List createFileDownloadMessagePieces(String sOwner, String sFilename) {
        File oFile = new File(sFilename);
        String sFileContent = "";
        try {
            sFileContent = FileUtil.getBase64EncodeFromFile(sFilename);
        } catch (IOException ex) {
            Logger.getLogger(MessageMSPFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
        int pieces = sFileContent.length() / pieceSize;
        List<Message> messages = new ArrayList<Message>();

        for (int i = 0; i < pieces; i++) {
            Message oMessage = createDefaultMessage(sOwner, ServiceType.FILE_DOWNLOAD.getName());
            addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
            addIntegerToMessage(oMessage, "" + Attributes.TAG_PIECE_ID, i);
            addStringToMessage(oMessage, "" + Attributes.TAG_MESSAGE_ID, sOwner + "_DOWNLOAD_" + i);
            String sCurrentContent = "";
            if (((i + 1) * pieceSize) < sFileContent.length()) {
                sCurrentContent = sFileContent.substring(pieceSize * i, pieceSize * (i + 1));
            } else {
                sCurrentContent = sFileContent.substring(pieceSize * i, sFileContent.length());
            }
            try {
                addByteArrayToMessage(oMessage, "" + Attributes.TAG_FILE_CONTENT, sCurrentContent.getBytes(), false);
            } catch (IOException ex) {
                Logger.getLogger(MessageMSPFactory.class.getName()).log(Level.SEVERE, null, ex);
            }
            messages.add(oMessage);
        }
        return messages;
    }

    public static Message createFileUploadSessionMessage(String sOwner, String sFilename, int pieces, int index) {
        Message oMessage = createDefaultMessage(sOwner, ServiceType.FILE_UPLOAD.getName());
        File oFile = new File(sFilename);
        addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
        addIntegerToMessage(oMessage, null, "" + Attributes.TAG_SESSION_PIECES, pieces);
        addIntegerToMessage(oMessage, null, "" + Attributes.TAG_START_INDEX, index);

        return oMessage;
    }

    public static Message createFileRecoverUploadSessionMessage(String sOwner, String sFilename, int pieces) {

        File oFile = new File(sFilename);
        File fileFolder = new File("MySavedStates/" + sOwner + "/UPLOAD/" + oFile.getName() + "/");

        if (fileFolder.exists() == false) {
            return null;
        }

        File lastStateFile = null;

        for (File f : fileFolder.listFiles()) {

            if (lastStateFile == null) {
                lastStateFile = f;
            } else {
                if (lastStateFile.getName().compareTo(f.getName()) < 0) {
                    lastStateFile = f;
                }
            }
        }

        int currentPiece = 0;

        Message oMessage = createDefaultMessage(sOwner, ServiceType.RECOVER_FILE_UPLOAD.getName());
        
        addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
        addIntegerToMessage(oMessage, null, "" + Attributes.TAG_SESSION_PIECES, pieces);
        addIntegerToMessage(oMessage, null, "" + Attributes.TAG_CURRENT_PIECE, currentPiece);

        return oMessage;
    }

    public static Message createFileDownloadSessionMessage(String sOwner, String sFilename, int pieces) {
        Message oMessage = createDefaultMessage(sOwner, ServiceType.FILE_DOWNLOAD.getName());
        File oFile = new File(sFilename);
        addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
        addIntegerToMessage(oMessage, null, "" + Attributes.TAG_SESSION_PIECES, pieces);

        return oMessage;
    }

    public static Message createFileRecoverDownloadSessionMessage(String sOwner, String sFilename, int pieces) {
        File oFile = new File(sFilename);
        File fileFolder = new File("MySavedStates/" + sOwner + "/DOWNLOAD/" + oFile.getName() + "/");

        if (fileFolder.exists() == false) {
            return null;
        }

        File lastStateFile = null;

        for (File f : fileFolder.listFiles()) {

            if (lastStateFile == null) {
                lastStateFile = f;
            } else {
                if (lastStateFile.getName().compareTo(f.getName()) < 0) {
                    lastStateFile = f;
                }
            }
        }

        int currentPiece = 0;

        Message oMessage = createDefaultMessage(sOwner, ServiceType.RECOVER_FILE_DOWNLOAD.getName());
        addStringToMessage(oMessage, "" + Attributes.TAG_FILE_NAME, "" + oFile.getName());
        addIntegerToMessage(oMessage, null, "" + Attributes.TAG_SESSION_PIECES, pieces);
        addIntegerToMessage(oMessage, null, "" + Attributes.TAG_CURRENT_PIECE, currentPiece);

        return oMessage;
    }

    public static Message createDefaultMessage(String sOwner, String sProcessID) {
        Message oMessage = new Message();
        long time = System.currentTimeMillis();
        addStringToMessage(oMessage, "" + Attributes.TAG_SHARED_KEY, "" + time);
        addUnsecureStringToMessage(oMessage, "" + Attributes.TAG_PROCESS_ID, "" + sProcessID);
        addStringToMessage(oMessage, "" + Attributes.TAG_OWNER, sOwner);
        addUnsecureStringToMessage(oMessage, "" + Attributes.TAG_SEC_DIGEST, "");
        return oMessage;
    }


     /**
     * Prints message element names and content and some stats
     *
     * @param msg     message to print
     * @param verbose indicates whether to print elment content
     */
    public static void printMessageStats(Message msg, boolean verbose) {
        try {
            System.out.println("------------------Begin Message---------------------");
            WireFormatMessage serialed = WireFormatMessageFactory.toWire(msg, new MimeMediaType("application/x-jxta-msg"), null);
            System.out.println("Message Size :" + serialed.getByteLength());
            ElementIterator it = msg.getMessageElements();

            while (it.hasNext()) {
                MessageElement el = it.next();
                System.out.println("Element : " + it.getNamespace() + " :: " + el.getElementName());
                if (verbose) {
                    System.out.println("[" + el + "]");
                }
            }
            System.out.println("-------------------End Message----------------------");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static enum Attributes {
        TAG_SEC_DIGEST,
        TAG_SEC_DH_PUBLIC_KEY,
        TAG_START_INDEX,
        TAG_MESSAGE_ID,
        TAG_PIECE_ID,
        TAG_SESSION_PIECES,
        TAG_CURRENT_PIECE,
        TAG_SHARED_KEY,
        TAG_MESSAGES,
        TAG_OWNER,
        TAG_FILE_NAME,
        TAG_FILE_CONTENT,
        TAG_PROCESS_ID,
        TAG_OPERATION_RESULT,
        TAG_VALUE,
        TAG_OPERATION,
        TAG_RESPONSE_FOR,
        TAG_FLOW_STEP,
        TAG_APP_COMMAND,
        TAG_APP_OUTPUT,
        TAG_TIME_OUTPUT,
        TAG_STATE_FLOW_ID
    };

    public static enum StateTags {

        MARK,
        ACK
    };
}
